import contextvars
from functools import wraps

from fastapi import HTTPException, status
from fastapi import Request

from app.auth.models import User, UserWhole
from app.auth.services import auth_service
from app.core import security
from app.core.middlewares import get_request_context

current_user_var = contextvars.ContextVar("current_user_var")


async def get_current_user():
    try:
        return current_user_var.get()
    except LookupError:
        request = get_request_context()
        try:
            current_user = await fetch_current_user(request)
            current_user_var.set(current_user)
            return current_user
        except HTTPException as x:
            current_user_var.set(None)
            raise x


async def fetch_current_user(request: Request) -> UserWhole:
    token = get_token_from_request(request)

    payload = security.decode_access_token(token)
    if payload is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid access token",
        )
    user_id: int = payload.get("sub_id")
    if user_id is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",
        )
    user = await auth_service.get_user_with_roles(user_id)
    if user is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not found",
        )
    if user.status < User.STATUS_ACTIVE:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Inactive user",
        )
    return user


def get_token_from_request(request: Request):
    token = None
    auth_header = request.headers.get("Authorization")
    if auth_header and auth_header.startswith("Bearer "):
        token = auth_header[len("Bearer "):]
    if not token:
        token = request.query_params.get("token")
    if not token:
        token = request.cookies.get("token")
    return token


def create_access_token(username: str, user_id: int):
    return security.create_access_token({"sub": username, "sub_id": user_id})


def require_roles(*required_role_names: str):
    def decorator(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            sub_user: UserWhole = await get_current_user()
            if sub_user is None:
                raise HTTPException(
                    status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                    detail="Subject not found",
                )
            sub_roles = sub_user.roles
            if sub_roles is None:
                sub_roles = await auth_service.get_roles_by_user_id(sub_user.id)
            role_names = [role.name for role in sub_roles]
            if not any(role_name in role_names for role_name in required_role_names):
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Operation not permitted",
                )
            result = await func(*args, **kwargs)
            return result

        return wrapper

    return decorator
